package top.eggcode.setup.handler;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.shiro.ShiroException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import top.eggcode.common.consts.ResponseStatus;
import top.eggcode.common.error.ApplicationException;
import top.eggcode.common.error.InvalidParameterException;
import top.eggcode.common.mail.MailService;
import top.eggcode.common.mail.MailTemplate;
import top.eggcode.common.model.ResponseResult;
import top.eggcode.common.mvc.Responser;
import top.eggcode.common.thread.AsyncExecutor;
import top.eggcode.plugins.notice.dingtalk.DingMessage;
import top.eggcode.plugins.notice.dingtalk.DingRobotClientImpl;
import top.eggcode.plugins.notice.dingtalk.DingTalkRobotProperties;
import top.eggcode.setup.property.AlertProperty;
import top.eggcode.system.modules.log.model.ExceptionLogEntity;
import top.eggcode.system.modules.log.service.ExceptionLogServiceImpl;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import java.util.concurrent.ExecutionException;


/**
 * @author JiaQi Ding
 * Date: Created in 2018/3/22 11:02
 * Utils: Intellij Idea
 * Description: 全局异常处理
 */
@Slf4j
@RestControllerAdvice
@AllArgsConstructor
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    private final ExceptionLogServiceImpl exceptionLogService;

    private final MailService mailService;

    private final AsyncExecutor executor;

    private final DingTalkRobotProperties robotProperty;

    private final AlertProperty alarmProperty;

    /**
     * 记录异常日志
     *
     * @param e   异常对象
     * @param <T> 异常类型
     */
    private <T extends Throwable> void record(T e) {
        String stackTrace = ExceptionUtils.getStackTrace(e);
        String classType = e.getClass().getName();
        String message = e.getLocalizedMessage() == null ? "" : e.getLocalizedMessage();
        StackTraceElement stackTraceElement = e.getStackTrace()[0];
        int lineNumber = stackTraceElement.getLineNumber();
        String method = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName();

        executor.commit(() -> {
            ExceptionLogEntity log = new ExceptionLogEntity();
            log.setMessage(message);
            log.setExceptionClass(classType);
            log.setStackTrace(stackTrace);
            log.setLineNumber(lineNumber);
            log.setLocation(method);
            exceptionLogService.put(log);
        });

        // 代码异常向管理员发送通知
        if (!(e instanceof InvalidParameterException)) {
            executor.commit(() -> {
                logger.debug("发送邮件通知");

                String htmlBody = "<h2>".concat("错误描述：").concat("</h2>")
                        .concat("<p>").concat(message).concat("</p>")
                        .concat("<h2>").concat("触发方法：").concat("</h2>")
                        .concat("<p>").concat(method).concat("</p>")
                        .concat("<h2>").concat("文件行数：").concat("</h2>")
                        .concat("<p>").concat(String.valueOf(lineNumber)).concat("</p>")
                        .concat("<h2>").concat("堆栈详情：").concat("</h2>")
                        .concat("<p>").concat(stackTrace.substring(0, 500)).concat("</p>");
                MailTemplate mail = new MailTemplate();

                mail.setFromAlias("Sense-告警中心");
                mail.setSubject("发生业务异常");
                mail.setHtmlBody(htmlBody);
                mail.setToAddress(alarmProperty.getNoticeEmail());
                try {
                    mailService.sendSignle(mail);
                } catch (ExecutionException | InterruptedException executionException) {
                    executionException.printStackTrace();
                    logger.error("邮件通知失败");
                }
                logger.debug("发送邮件通知，成功");

                logger.debug("发送钉钉通知");
                // 钉钉通知
                DingRobotClientImpl client = new DingRobotClientImpl(robotProperty.getWebhookUrl(), robotProperty.getSecret());

                String markdown = "## ".concat("SenseBoot [系统异常]").concat("\n")
                        .concat("### ").concat("异常描述：").concat("\n")
                        .concat(message).concat("\n")
                        .concat("### ").concat("触发方法：").concat("\n")
                        .concat(method).concat("\n")
                        .concat("### ").concat("代码行数：").concat("\n")
                        .concat(String.valueOf(lineNumber)).concat("\n")
                        .concat("### ").concat("堆栈详情：").concat("\n")
                        .concat(stackTrace.substring(0, 500));

                DingMessage dingMessage = DingMessage.builder()
                        .setMarkdownContent("发生系统异常", markdown)
                        .atMobile(alarmProperty.getDingtalkMobile())
                        .toMessage();

                try {
                    client.pushMessage(dingMessage);
                } catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeyException ex) {
                    ex.printStackTrace();
                    logger.error("钉钉通知失败");
                }
                logger.debug("发送钉钉通知，成功");

            });
        }
    }

    /* 系统自定义异常 -------------------------------------------*/

    /**
     * 业务异常
     */
    @ExceptionHandler(value = ApplicationException.class)
    public ResponseResult<?> processException(ApplicationException e) {
        log.error("位置:{} -> 错误信息:{}", e.getLocation(), e.getLocalizedMessage());
        record(e);
        return Responser.failure(e.getCode(), e.getMessage());
    }

    /* 系统组件异常 ---------------------------------------------------*/

    /**
     * 拦截表单参数校验
     */
    @org.springframework.web.bind.annotation.ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({BindException.class})
    public ResponseResult<?> bindException(BindException e) {
        record(e);
        BindingResult bindingResult = e.getBindingResult();
        return Responser.failure(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
    }

    /**
     * 拦截JSON参数校验
     */
    @org.springframework.web.bind.annotation.ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseResult<?> bindException(MethodArgumentNotValidException e) {
        record(e);
        BindingResult bindingResult = e.getBindingResult();
        return Responser.failure(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
    }

    /**
     * 参数格式错误
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseResult<?> methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        record(e);
        log.error("错误信息{}", e.getLocalizedMessage());
        return Responser.failure(ResponseStatus.REQ_ARGUMENT_TYPE_MISMATCH);
    }

    /**
     * 参数格式错误
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseResult<?> httpMessageNotReadable(HttpMessageNotReadableException e) {
        record(e);
        log.error("错误信息:{}", e.getLocalizedMessage());
        return Responser.failure(ResponseStatus.REQ_FORMAT_ERROR);
    }

    /**
     * 请求方式不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseResult<?> httpReqMethodNotSupported(HttpRequestMethodNotSupportedException e) {
        record(e);
        log.error("错误信息:{}", e.getLocalizedMessage());
        return Responser.failure(ResponseStatus.REQ_METHOD_NOT_SUPPORT);
    }

    /**
     * 基础异常 Exception
     */
    @org.springframework.web.bind.annotation.ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(Exception.class)
    public ResponseResult<?> exception(Exception e) {
        record(e);
        e.printStackTrace();
        return Responser.failure(ResponseStatus.SYSTEM_EXCEPTION);
    }

    /**
     * 空指针异常
     */
    @ExceptionHandler(NullPointerException.class)
    public ResponseResult<?> nullPointerException(HttpServletRequest req, NullPointerException e) {
        record(e);
        log.error("错误信息:{}", e.getLocalizedMessage());
        e.printStackTrace();
        return Responser.failure(ResponseStatus.SYSTEM_NULL_POINTER);
    }

    /* shiro 异常 --------------------------------------------------------*/

    /**
     * 无效token
     */
    @ExceptionHandler(IncorrectCredentialsException.class)
    public ResponseResult<?> handleIncorrectCredentialsException(IncorrectCredentialsException e) {
        record(e);
        log.error("错误信息:{}", e.getLocalizedMessage());
        return Responser.failure(ResponseStatus.USER_INVALID_TOKEN);
    }

    /**
     * 身份认证失败
     */
    @ExceptionHandler(AuthenticationException.class)
    public ResponseResult<?> handleAuthenticationException(AuthenticationException e) {
        record(e);
        log.error("错误信息:{}", e.getLocalizedMessage());
        return Responser.failure(ResponseStatus.USER_AUTHORIZATION_FAIL);
    }

    /**
     * 身份认证失败
     */
    @ExceptionHandler(ShiroException.class)
    public ResponseResult<?> handleShiroException(ShiroException e) {
        record(e);
        log.error("错误信息:{}", e.getLocalizedMessage());
        return Responser.failure(ResponseStatus.SYSTEM_SHIRO_EXCEPTION);
    }

    /**
     * 未授权
     */
    @ExceptionHandler(UnauthorizedException.class)
    public ResponseResult<?> handleUnauthorizedException(UnauthorizedException e) {
        record(e);
        log.error("错误信息:{}", e.getLocalizedMessage());
        return Responser.failure(ResponseStatus.USER_UNAUTHORIZED);
    }

    /**
     * 未认证
     * 可能是token无效、错误、账户不存在等
     */
    @ExceptionHandler(UnauthenticatedException.class)
    public ResponseResult<?> handleUnauthenticatedException(UnauthenticatedException e) {
        record(e);
        log.error("错误信息:{}", e.getLocalizedMessage());
        return Responser.failure(ResponseStatus.USER_UNAUTHENTICATED);
    }

    /**
     * token过期
     */
    @ExceptionHandler(ExpiredCredentialsException.class)
    public ResponseResult<?> handleExpiredCredentialsException(ExpiredCredentialsException e) {
        record(e);
        log.error("错误信息:{}", e.getLocalizedMessage());
        return Responser.failure(ResponseStatus.USER_OVERDUE_TOKEN);
    }
}
